#include "IFD.h"
#include <length.h>
#include <IPC/streamIO.h>
#include <OSProc.h>
#include "LLib.h"
#include <MC.h>
using namespace IFDInterface;
using namespace LibMemory;
using namespace LazyLib;

#define _MEDIA(fstr)   ((_media*)(fstr->object))
#define __WriteFile(fstr,from,num,offset)  (_MEDIA(fstr)->func->file_write((void**)_MEDIA(fstr)->inside,(const char*)from,num,offset))
#define __ReadFile(fstr,to,num,offset)     (_MEDIA(fstr)->func->file_read((void**)_MEDIA(fstr)->inside,(char*)to,num,offset))
//constexpr _ptlong PAGE_SIZE = 1 << 12;


namespace IFD
{
  extern _dsope(_fstr) IFDOpe;
  volatile _pcb* IFDPro = NULL;
  volatile _llibs drives;
  memBlocks edge(edgeStart,edgeEnd,PAGE_SIZE);

  memBlocks drv(drvStart,drvEnd,PAGE_SIZE);
  typedef struct
  {
    void* inside;
    ibuffs buffers;
    _file_op* func;
    _ptlong  layer;  //level of layer. Give an abstract to the GetFileSysHandle GetUpperHandle...
    _ptlong drvcode;
    _libs* drvlib;
  }_media;
  
static inline FRESULT IFDWriteOperation(_fstr stream,_ptlong* from,const _length& num,const _length& offset);
static inline FRESULT IFDReadOperation(_fstr stream,_ptlong* to,const _length& num,const _length& offset);
static inline _fstr GetDrive(_ptlong drvcode);

static inline  _ptlong IFDConvertToPub(_fstr stream,_dfps streamto,_length num,_length offset)
{
  _ptlong size = num;
  _ptlong* addr = edge.PopGroup((size % PAGE_SIZE == 0)?(size / PAGE_SIZE):(size / PAGE_SIZE) + 1);
  _ptlong ret;
 
  if((ret = IFDReadOperation(stream,addr,num,offset))!= FR_OK)
    return ret;
  WriteStream(streamto,(u8*)addr,num,offset);
  edge.PushGroup(addr);

}

static inline  _ptlong IFDConvertToPrv(_dfps stream, _fstr streamto,_length num,_length offset)
{
  _ptlong size = num;
  _ptlong* addr = edge.PopGroup((size % PAGE_SIZE == 0)?(size / PAGE_SIZE):(size / PAGE_SIZE) + 1);
  ReadStream(streamto,(u8*)addr,num,offset);
  _ptlong ret = IFDWriteOperation(streamto,addr,num,offset);
  edge.PushGroup(addr);
  return ret;
}

static _ptlong IFDMountStream(_fstr stream,_fstr streamto)
{
  DelegateStream(stream,(_pcb*)IFDPro,&IFDOpe);
}

static _ptlong IFDUnmountStream(_fstr stream,_fstr streamto)
{

}

static _ptlong IFDNameStream(char* name,_fstr streamto)
{

}
static inline FRESULT _WriteFile(_fstr stream,_ptlong* from,const _length& num,const _length& offset)
{
  _libs* drvlib;
  _fstr drvfile = GetDrive((_ptlong)stream);
  MUTEX_LOCK(&(drvfile->streamlocker));
    {
      if(drvfile->lib == NULL)
	drvlib = drvfile->lib = LoadLib((_llibs*)&drives,drvfile);
      else
	drvlib = drvfile->lib;
      //      MUTEX_LOCK(drvlib->lock);  
      {
	drvlib->refered+=1;
      }
      //      MUTEX_UNLOCK(drvlib->lock);
    }
  MUTEX_UNLOCK(&(drvfile->streamlocker));

  
  __WriteFile(stream,from,num,offset);
  MUTEX_LOCK(&(drvfile->streamlocker));
  {
    // MUTEX_LOCK(drvlib->lock);
    {
      if(drvlib->refered == 1)
	UnloadLib((_llibs*)&drives,drvlib);
    }
    // MUTEX_UNLOCK(drvlib->lock);
  }
  MUTEX_UNLOCK(&(drvfile->streamlocker));
  
}
static inline FRESULT IFDWriteOperation(_fstr stream,_ptlong* from,const _length& num,const _length& offset)
{
  FRESULT ret;
  return ((ret =  _WriteFile(stream,from,num,offset)) == FR_OK
	  ?BufferSync(&(_MEDIA(stream)->buffers),from,num,offset)
	  :ret);
}

static inline FRESULT _ReadFile(_fstr stream,_ptlong* to,const _length& num, const _length& offset)
{
  _libs* drvlib;
  _fstr drvfile = GetDrive((_ptlong)stream);
  MUTEX_LOCK(&(drvfile->streamlocker));
    {
      if(drvfile->lib == NULL)
	drvlib = drvfile->lib = LoadLib((_llibs*)&drives,drvfile);
      else
	drvlib = drvfile->lib;
      //      MUTEX_LOCK(drvlib->lock);  
      {
	drvlib->refered+=1;
      }
      //      MUTEX_UNLOCK(drvlib->lock);
    }
  MUTEX_UNLOCK(&(drvfile->streamlocker));

  
  __ReadFile(stream,to,num,offset);
  MUTEX_LOCK(&(drvfile->streamlocker));
  {
    // MUTEX_LOCK(drvlib->lock);
    {
      if(drvlib->refered == 1)
	UnloadLib((_llibs*)&drives,drvlib);
    }
    // MUTEX_UNLOCK(drvlib->lock);
  }
  MUTEX_UNLOCK(&(drvfile->streamlocker));

}

static inline FRESULT IFDReadOperation(_fstr stream,_ptlong* to,const _length& num,const _length& offset)
{
  return BufferGet(&(_MEDIA(stream)->buffers),to,num,offset,[&](const _length& _num,const _length& _offset,_ptlong* _to)->FRESULT
		   {
		     return _ReadFile(stream,_to,_num,_offset);
		   });
}




static inline _fstr GetDrive(_ptlong drvcode)
{
  
}

  _dsope(_fstr) IFDOpe=
  {
    IFDConvertToPub,IFDConvertToPrv,0,0,IFDMountStream,IFDUnmountStream,IFDNameStream
  };
}
using namespace IFD;
  int IFD_Init()
  {
    InitLazyLib((_llibs*)&drives,(_pcb*)IFDPro,[](_ptlong size)->_ptlong*{return (_ptlong*)_Malloc(size);},
		[](_ptlong allocSize){return drv.PopGroup((allocSize % PAGE_SIZE) == 0?allocSize / PAGE_SIZE :((allocSize/PAGE_SIZE)+1));},
		[](_ptlong* freeptr){return drv.PushGroup(freeptr);});
  }



